#include "StdAfx.h"
#include "SelectionCondition.h"
#include "SelectionVariables.h"


class SimpleLexer
{
public:
	SimpleLexer(const char* buffer)
		: m_buffer(buffer)
		, m_obuffer(buffer)
	{
	}

	int peek(string* ident = 0)
	{
		const char* obuffer = m_buffer;
		string oident(m_ident);
		int tok = operator()();
		m_buffer = obuffer;
		if (ident)
			ident->swap(m_ident);
		m_ident.swap(oident);

		return tok;
	}

	const char* ident() const
	{
		return m_ident.c_str();
	}

	const char* buf() const
	{
		return m_obuffer;
	}


	int operator()()
	{
		while (unsigned char ch = *m_buffer++)
		{
			unsigned char next = *m_buffer;

			switch (ch)
			{
			case '(':
			case ')':
				return (int)ch;
			case '=':
				if (next == '=')
				{
					++m_buffer;
					return '==';
				}
				return ch;
			case '!':
				if (next == '=')
				{
					++m_buffer;
					return '!=';
				}
				return ch;
			default:
				{
					if (isalpha(ch) || (ch == '_'))
					{
						m_ident.clear();
						m_ident.push_back(ch);
						while ((ch = *m_buffer) && (isalnum(ch) || (ch == '.') || (ch == '_')))
						{
							++m_buffer;
							m_ident.push_back(ch);
						}

						// keywords
						if (!stricmp(m_ident.c_str(), "or"))
							return 'or';
						else if (!stricmp(m_ident.c_str(), "and"))
							return 'and';
						else if (!stricmp(m_ident.c_str(), "xor"))
							return 'xor';
						else if (!stricmp(m_ident.c_str(), "true"))
							return 'true';
						else if (!stricmp(m_ident.c_str(), "false"))
							return 'fals';
						return 'var';
					}
					else if (isspace(ch))
						continue;

					return ch;
				}
			}
		}

		return 0;
	}

private:
	const char* m_buffer;
	const char* m_obuffer;
	string m_ident;
};


SelectionCondition::SelectionCondition()
: m_rootID(-1)
{
}

SelectionCondition::SelectionCondition(const char* condition, const SelectionVariableDeclarations& variables)
: m_rootID(Parse(condition, variables))
{
	if (m_rootID >= 0)
		Optimise();
}

int SelectionCondition::AddOp(const ConditionOp& op)
{
	m_conditionOps.push_back(op);
	return (int)m_conditionOps.size() - 1;
}

int SelectionCondition::ParseLogical(SimpleLexer& lex, int tok, const SelectionVariableDeclarations& variables)
{
	int leftID = ParseCompOp(lex, tok, variables);
	if (leftID == -1)
		return -1;

	while ((tok = lex.peek()) && (tok == 'and') || (tok == 'or') || (tok == 'xor'))
	{
		lex();

		int rightID = ParseCompOp(lex, lex(), variables);
		if (rightID == -1)
			return -1;

		switch (tok)
		{
		case 'or':
			leftID = AddOp(ConditionOp(ConditionOp::Or, leftID, rightID));
			break;
		case 'and':
			leftID = AddOp(ConditionOp(ConditionOp::And, leftID, rightID));
			break;
		case 'xor':
			leftID = AddOp(ConditionOp(ConditionOp::Xor, leftID, rightID));
			break;
		}
	}

	return leftID;
}

int SelectionCondition::ParseCompOp(SimpleLexer& lex, int tok, const SelectionVariableDeclarations& variables)
{
	int leftID = ParseUnary(lex, tok, variables);
	if (leftID == -1)
		return -1;

	tok = lex.peek();
	if ((tok == '==') || (tok == '!='))
	{
		lex();

		int rightID = ParseUnary(lex, lex(), variables);
		if (rightID == -1)
			return -1;

		switch (tok)
		{
		case '==':
			return AddOp(ConditionOp(ConditionOp::Equal, leftID, rightID));
		case '!=':
			return AddOp(ConditionOp(ConditionOp::NotEqual, leftID, rightID));
		}
	}

	return leftID;
}

int SelectionCondition::ParseUnary(SimpleLexer& lex, int tok, const SelectionVariableDeclarations& variables)
{
	if (tok == '!')
	{
		int opID = ParseValue(lex, lex(), variables);
		if (opID != -1)
			return AddOp(ConditionOp(ConditionOp::Not, opID, -1));
		return -1;
	}

	return ParseValue(lex, tok, variables);
}

int SelectionCondition::ParseValue(SimpleLexer& lex, int tok, const SelectionVariableDeclarations& variables)
{
	if (tok == '(')
	{
		int opID = ParseLogical(lex, lex(), variables);
		if ((opID == -1) || (lex() != ')'))
			return -1;

		return opID;
	}
	else if (tok == 'true')
		return AddOp(ConditionOp(ConditionOp::Constant, true));
	else if (tok == 'fals')
		return AddOp(ConditionOp(ConditionOp::Constant, false));
	else if (tok == 'var')
	{
		if (SelectionVariableID variableID = variables.GetVariableID(lex.ident()))
			return AddOp(ConditionOp(ConditionOp::Variable, variableID));
		else
		{
			AIWarning("Unknown variable '%s' used in condition '%s'...", lex.ident(), lex.buf());
		}
	}

	return -1;
}

int SelectionCondition::Parse(const char* condition, const SelectionVariableDeclarations& variables)
{
	SimpleLexer lex = SimpleLexer(condition);
	return ParseLogical(lex, lex(), variables);
}

void SelectionCondition::Optimise()
{
	// TODO(Marcio)
}

bool SelectionCondition::EvaluateOp(const SelectionVariables& variables, const ConditionOp& op) const
{
	switch (op.opType)
	{
	case ConditionOp::Variable:
		{
			bool value = false;
			variables.GetVariable(op.variableID, &value);

			return value;
		}
	case ConditionOp::Constant:
		return op.value;
	case ConditionOp::Or:
		return EvaluateOp(variables, m_conditionOps[op.operandLeft]) 
			|| EvaluateOp(variables, m_conditionOps[op.operandRight]);
	case ConditionOp::And:
		return EvaluateOp(variables, m_conditionOps[op.operandLeft])
			&& EvaluateOp(variables, m_conditionOps[op.operandRight]);
	case ConditionOp::Xor:
		return EvaluateOp(variables, m_conditionOps[op.operandLeft])
			^ EvaluateOp(variables, m_conditionOps[op.operandRight]);
	case ConditionOp::Not:
		return !EvaluateOp(variables, m_conditionOps[op.operandLeft]);
	case ConditionOp::Equal:
		return EvaluateOp(variables, m_conditionOps[op.operandLeft])
			== EvaluateOp(variables, m_conditionOps[op.operandRight]);
	case ConditionOp::NotEqual:
		return EvaluateOp(variables, m_conditionOps[op.operandLeft])
			!= EvaluateOp(variables, m_conditionOps[op.operandRight]);
	}

	return false;
}

bool SelectionCondition::Evaluate(const SelectionVariables& variables) const
{
	if (Valid())
		return EvaluateOp(variables, m_conditionOps[m_rootID]);

	return false;
}

bool SelectionCondition::Valid() const
{
	return m_rootID >= 0;
}